/****************************************************************************
 *
 *                Admin Over Im
 *
 ****************************************************************************
 *
 *  <Copyright>
 *    Copyright (C) 2008  Basov Alexander
 *
 *  <Unit> jabber.c
 *   <ImplementationNotes>
 *   <PortabilityIssues>
 *   <AnticipatedChanges>
 *****************************************************************************
 *
 *  $Revision: 140 $ $Author: algrub@yandex.ru $
 *  $Date: 2008-03-31 19:06:32 +0000 (Mon, 31 Mar 2008) $
 *****************************************************************************/


#include <cerrno>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

#include <unistd.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>

#include <libxml/tree.h>

#include "jabber.h"

using namespace std;

void Jabber::Connect (const string ip, const int port)
{

  struct sockaddr_in s;
  
  if ((this->sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
    err ("SOCKET");

  bzero (&s, sizeof (s));
  s.sin_family = AF_INET;
  s.sin_port = htons (port);
  
  if (inet_pton (AF_INET, ip.c_str(), &s.sin_addr) <= 0)
    err ("INET_PTON");

  if (connect (this->sockfd, (struct sockaddr*) &s, sizeof (s)) < 0)
    err ("CONNECT");
}

void Jabber::send (const string data)
{
  size_t b_left;
  ssize_t b_written;
  const char *ptr = data.c_str ();

  b_left = data.size();
  
  while (b_left > 0)
  {
    if ((b_written = write (this->sockfd, (void*)ptr, b_left)) <= 0)
    {
      if (errno == EINTR)
	b_written = 0;
      else
	err ("ERROR WHILE SENDING");
    }
    
    b_left -= b_written;
    ptr += b_written;  
  }
}

string Jabber::recieve (size_t bufSize)
{
  // Need to be rewritten
  char buf [bufSize];
  char *ptr;
  size_t nread;
  size_t nleft;
  string result;

  ptr = buf;
  nleft = bufSize;
  while (nleft > 0)
  {
    if ((nread = read (this->sockfd, ptr, nleft)) < 0)
    {
      if (errno == EINTR)
        nread = 0;
      else
        err ("READ");
    }
    else if (nread == 0)
      break;

    result.append (ptr);
    result.resize (nread);
    if (result.substr (result.length () - 1) == ">")
      break;

    nleft -= nread;
    ptr += nread;
    
    if (nread == bufSize)
      break;
  }

  return result;
}

// Simple Select Wrapper
int Jabber::Select(int nfds, 
	   fd_set *readfds, 
	   fd_set *writefds, 
	   fd_set *exceptfds,
	   struct timeval *timeout)
{
  int n;
  
  if ((n = select (nfds, readfds, writefds, exceptfds, timeout)) < 0)
    err("select error");
  return(n);		/* can return 0 on timeout */
}

// main loop, on receiving data from socket.
void Jabber::idle ()
{
  fd_set rset;
  string res;

  FD_ZERO (&rset);
  for (;;)
  {
    FD_SET (this->sockfd, &rset);
    Select (this->sockfd + 1, &rset, NULL, NULL, NULL);

    if (FD_ISSET (this->sockfd, &rset))
    {      
      res = this->recieve (BUFSIZ);
      if (res.find ("</stream") != string::npos)
      {
	cout << "Select = " << res << endl;
	break;
      }
      else
      {	
	jparse (res);
	//	cout << "Select = " << jparse (res) << endl;
	cout << "Select = " << res << endl;
      }	
    }     
  }
}

void Jabber::login (const string userName, const string password)
{
  IQAuthRequest *iqar = new IQAuthRequest ("aaaa", "", "ZION");
  iqar->setUserName (userName);
  iqar->setPassword (password);
  iqar->setResource ("aoi"); 
  // TODO: binding resource must be a separate procedure
  this->send (iqar->toXmlStr ());
}

void Jabber::OpenStream (const string to)
{
  
  // TODO: add check for empty value of to attribute

  xmlDocPtr doc = NULL;
  xmlNodePtr node = NULL;
  xmlChar* xmlbuf;
  int bufsiz;
  string result;
 

  /**
   * Creates a new document, a node and set it as a root node 
   */
  doc = xmlNewDoc (BAD_CAST "1.0");
  node = xmlNewNode (NULL, BAD_CAST "stream:stream");
  xmlDocSetRootElement (doc, node);
  
  /**
   * Creating requierd attributes for the stream tag.
   */
  xmlNewProp (node, BAD_CAST "to", BAD_CAST to.c_str());
  xmlNewProp (node, BAD_CAST "xmlns", BAD_CAST "jabber:client");
  xmlNewProp (node, 
	      BAD_CAST "xmlns:stream", 
	      BAD_CAST "http://etherx.jabber.org/streams");

  /**
   * Dumping document to the string  
   */
  xmlDocDumpFormatMemory (doc, &xmlbuf, &bufsiz, 1);  
  
  if (xmlbuf == NULL)
    err ("NULL Buffer returnd");

  result = string (reinterpret_cast<char*>(xmlbuf), bufsiz);

  /**
   * Free the document 
   */
  xmlFree (xmlbuf);
  xmlFreeDoc (doc);

  /**
   *  Free the global variables that may
   *  have been allocated by the parser.
   */
  xmlCleanupParser ();

  result = result.replace (result.length() - 3, 1,"");
  
  this->send (result);
}

const string Jabber::toXmlStringStatus (J_STATUS s)
{
  switch (s)
  {
    case AWAY:
      return "<show>away</show>";
      break;
    case CHAT:
      return "<show>chat<show>";
      break;
    case DND:
      return "<show>dnd</show>";
      break;      
    case UNAVAILABLE:
      return "<show>unavailable</show>";
      break;
    case XA:
      return "<show>xa</show>";
      break;
    case ONLINE:
    default:
      return "";
      break;
  }
}

void Jabber::setStatus (const J_STATUS s, const string msg)
{
  
  this->send ("<presence>" + 
    toXmlStringStatus (s) + 
    "<status>" + msg + "</status>" +
    "<prority>5</prority>" +
    "</presence>");
}

void Jabber::CloseStream ()
{
  this->_authOK = false;
  this->send ("\
<presence type='unavailable'>\
<status>By</status></presence>\
</stream:stream>");
  close (this->sockfd);
}

enum TAG {IQ_TAG, MESSAGE_TAG, OTHER};

struct pState
{
  TAG curTag;
  Jabber* thisPtr;
  IQAttrs attrs;
};

void getIQattrs (IQAttrs *result, const xmlChar **attributes)
{

  int i = 0;
  const xmlChar* attr = NULL;
  
  while (attr = attributes [i])
  {
    if (attr == NULL)
      break;
    if (xmlStrncasecmp (attr, BAD_CAST "from", 4) == 0)    
      result->fromAttr = (char*)attributes [i+1];
    else if (xmlStrncasecmp (attr, BAD_CAST "to", 2) == 0)
      result->toAttr = (char*)attributes [i+1];
    else if (xmlStrncasecmp (attr, BAD_CAST "id", 2) == 0)    
      result->idAttr = (char*)attributes [i+1];
    else if (xmlStrncasecmp (attr, BAD_CAST "type", 4) == 0)	      
      result->typeAttr = (char*)attributes [i+1];
    
    i += 2;
  }      
}

void getQueryAttr (IQAttrs *result, const xmlChar **attributes)
{
  int i = 0;
  const xmlChar* attr = NULL;
  
  while (attr = attributes [i])
  {
    if (attr == NULL)
      break;

    if (xmlStrncasecmp (attr, BAD_CAST "xmlns", 5) == 0)
      result->query = (char*)attributes [i+1];
    
    i += 2;
  }      
}

static void StartElement(void *voidContext,
                         const xmlChar *name,
                         const xmlChar **attributes)
{ 
  pState *ps = (pState*)voidContext;

  // Setting up current parsing tag
  if (xmlStrncasecmp (name, BAD_CAST "iq", 2) == 0)
    ps->curTag = IQ_TAG

  else if (xmlStrncasecmp (name, BAD_CAST "message", 7) == 0);
  {
    ps->curTag = MESSAGE_TAG;
    Jmessage message(attributes);
  }

  if (ps->curTag == IQ_TAG)
    if (xmlStrncasecmp (name, BAD_CAST "iq", 2) == 0)
    {
      if (attributes != NULL)       
      {
	getIQattrs (&(ps->attrs), attributes);
      }
      
    }
    else if (xmlStrncasecmp (name, BAD_CAST "query", 5) == 0)
    {
      if (attributes != NULL)
	getQueryAttr (&(ps->attrs), attributes);
    } 
}

static void EndElement(void *voidContext, const xmlChar *name)
{
  pState *ps = (pState*)voidContext;
  Jabber *j = ps->thisPtr;

  if (xmlStrncasecmp (name, BAD_CAST "iq", 2 ) == 0)    
  {
    j->onIQRecieve (j->getIqFactory()->create (ps->attrs));   
    ps->curTag = OTHER;    
  }
}

static void Characters(void *voidContext,
                       const xmlChar *chars,
                       int length)
{
  char *buf = (char*)malloc (length);

  strncpy (buf, (char*)chars, length);
  if (buf !=NULL)
    free (buf);
}

static xmlSAXHandler saxHandler =
{
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  StartElement,
  EndElement,
  NULL,
  Characters,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};

void Jabber::jparse (const string data)
{
  pState p;
  p.thisPtr = this;
  p.curTag = OTHER;
  p.attrs = IqFactory::emptyAttrs ();
  xmlParserCtxtPtr ctxt;

  ctxt = xmlCreatePushParserCtxt(&saxHandler, &p, "", 0, "");

  xmlParseChunk(ctxt, data.c_str(), data.size(), 0);
  xmlParseChunk(ctxt, "", 0, 1);

  xmlFreeParserCtxt(ctxt);
}

void Jabber::onIQRecieve (IQ *IQVal)
{  
  cout << "onIQRecieve : " << IQVal <<endl;

  if (IQVal)  
    switch (IQVal->className ())
    {   
      case IQ_RESULT:
	if (dynamic_cast<IQResult*>(IQVal)->getType () == RESULT)
	  if (dynamic_cast<IQResult*>(IQVal)->getId () == "aaaa")
	  {
	    this->_authOK = true;
	    this->onAuthOK ();
	  }	   
	  else
	  {
	    cout << "OTHER ID is not implemented yet";
	  }	   
	break;
      case IQ_VERSION_REQUEST:
	if (dynamic_cast<IQVersionRequest*>(IQVal)->getType () == RESULT)
	  this->onAuthOK ();
	break;

      case IQ_SERVICE_DISCOVERY_REQUEST:
	if 
	  (dynamic_cast<IQServiceDiscoveryRequest*>(IQVal)->getType() == GET)
	  this->onServiseDiscoveryRequest 
	    (dynamic_cast<IQServiceDiscoveryRequest*>(IQVal)->getId (),
	     dynamic_cast<IQServiceDiscoveryRequest*>(IQVal)->getFromJID ());
	break;	
	
      case IQ_BASE:
	// TODO: raise program error
	break;
      default:
	// TODO: raise program error
	break;     
      }
}

// ---------------------------
// Public API

// MUST be a special class for response
void Jabber::onServiseDiscoveryRequest (const string id, const string from)
{
  this->send ("<iq type='result' to='" + from + "' id='" + id + "'>	\
	       <query xmlns='http://jabber.org/protocol/disco#info'/>	\
               </iq>");
}

